/*=============================================================================

  Library: CFS

  Copyright (c) German Cancer Research Center,
    Division of Medical and Biological Informatics

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

=============================================================================*/

#ifndef CFSHIGHPRECISIONTIMER_H
#define CFSHIGHPRECISIONTIMER_H


#include <qglobal.h>

#undef _CFS_use_high_precision_timer_fallback

#ifdef Q_OS_MAC
#include <mach/mach_time.h>
#elif defined(Q_OS_UNIX)
#include <time.h>
#include <unistd.h>
#ifndef _POSIX_MONOTONIC_CLOCK
#warning Monotonic clock support missing on this POSIX platform
#define _CFS_use_high_precision_timer_fallback
#endif
#elif defined(Q_OS_WIN)
#include <windows.h>
#else
#define _CFS_use_high_precision_timer_fallback
#endif

#ifdef _CFS_use_high_precision_timer_fallback
#warning CFSHighPrecisionTimer is using the slower QTime fallback
#include <QTime>
#endif


/**
 * \ingroup Core
 *
 *
 * @brief A fast and high precision timer.
 *
 * This class provides a fast and high precision timer depending on
 * platform specific API. It can be used as a QTime replacement for
 * runtime measurements with a minimal performance overhead.
 */
class CFSHighPrecisionTimer {

public:

  inline CFSHighPrecisionTimer();

  inline void start();

  inline qint64 elapsedMilli();

  inline qint64 elapsedMicro();

private:

  // only used on MacOS, but needs to be defined in the .cpp file
  static double timeConvert;

#ifdef _CFS_use_high_precision_timer_fallback
  QTime startTime;
#elif defined(Q_OS_MAC)
  quint64 startTime;
#elif defined(Q_OS_UNIX)
  timespec startTime;
#elif defined(Q_OS_WIN)
  LARGE_INTEGER timerFrequency;
  LARGE_INTEGER startTime;
#endif
};

#ifdef _CFS_use_high_precision_timer_fallback

inline CFSHighPrecisionTimer::CFSHighPrecisionTimer()
  : startTime(QTime::currentTime())
{
}

inline void CFSHighPrecisionTimer::start()
{
  startTime = QTime::currentTime();
}

inline qint64 CFSHighPrecisionTimer::elapsedMilli()
{
  return startTime.elapsed();
}

inline qint64 CFSHighPrecisionTimer::elapsedMicro()
{
  return startTime.elapsed() * 1000;
}

#elif defined(Q_OS_MAC)

inline CFSHighPrecisionTimer::CFSHighPrecisionTimer()
: startTime(0)
{
  if (timeConvert == 0)
  {
    mach_timebase_info_data_t timeBase;
    mach_timebase_info(&timeBase);
    timeConvert = static_cast<double>(timeBase.numer) / static_cast<double>(timeBase.denom) / 1000.0;
  }
}

inline void CFSHighPrecisionTimer::start()
{
  startTime = mach_absolute_time();
}

inline qint64 CFSHighPrecisionTimer::elapsedMilli()
{
  quint64 current = mach_absolute_time();
  return static_cast<double>(current - startTime) * timeConvert / 1000.0;
}

inline qint64 CFSHighPrecisionTimer::elapsedMicro()
{
  quint64 current = mach_absolute_time();
  return static_cast<double>(current - startTime) * timeConvert;
}

#elif defined(Q_OS_UNIX)

inline CFSHighPrecisionTimer::CFSHighPrecisionTimer()
{
  startTime.tv_nsec = 0;
  startTime.tv_sec = 0;
}

inline void CFSHighPrecisionTimer::start()
{
  clock_gettime(CLOCK_MONOTONIC, &startTime);
}

inline qint64 CFSHighPrecisionTimer::elapsedMilli()
{
  timespec current;
  clock_gettime(CLOCK_MONOTONIC, &current);
  return (static_cast<qint64>(current.tv_sec)*1000 + current.tv_nsec/1000/1000) -
      (static_cast<qint64>(startTime.tv_sec)*1000 + startTime.tv_nsec/1000/1000);
}

inline qint64 CFSHighPrecisionTimer::elapsedMicro()
{
  timespec current;
  clock_gettime(CLOCK_MONOTONIC, &current);
  return (static_cast<qint64>(current.tv_sec)*1000*1000 + current.tv_nsec/1000) -
      (static_cast<qint64>(startTime.tv_sec)*1000*1000 + startTime.tv_nsec/1000);
}

#elif defined(Q_OS_WIN)

#include "CFSException.h"

inline CFSHighPrecisionTimer::CFSHighPrecisionTimer()
{
  if (!QueryPerformanceFrequency(&timerFrequency))
    throw CFSRuntimeException("QueryPerformanceFrequency() failed");
}

inline void CFSHighPrecisionTimer::start()
{
  //DWORD_PTR oldmask = SetThreadAffinityMask(GetCurrentThread(), 0);
  QueryPerformanceCounter(&startTime);
  //SetThreadAffinityMask(GetCurrentThread(), oldmask);
}

inline qint64 CFSHighPrecisionTimer::elapsedMilli()
{
  LARGE_INTEGER current;
  QueryPerformanceCounter(&current);
  return (current.QuadPart - startTime.QuadPart) / (timerFrequency.QuadPart / 1000);
}

inline qint64 CFSHighPrecisionTimer::elapsedMicro()
{
  LARGE_INTEGER current;
  QueryPerformanceCounter(&current);
  return (current.QuadPart - startTime.QuadPart) / (timerFrequency.QuadPart / (1000*1000));
}

#endif

#endif // CFSHIGHPRECISIONTIMER_H
